﻿using FCSChart.Axis;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using System.Threading;
using System.Linq;
using System.Collections.Concurrent;

namespace FCSChart.Series
{
    /// <summary>
    /// 直方图
    /// </summary>
    public class HistogramSeries : ISeries
    {

        /// <summary>
        /// 直方图等份数量
        /// </summary>
        public int Count
        {
            get { return (int)GetValue(CountProperty); }
            set { SetValue(CountProperty, value); }
        }
        public static readonly DependencyProperty CountProperty = DependencyProperty.Register("Count", typeof(int), typeof(HistogramSeries), new PropertyMetadata(512, NeedRedrawingProperty_Changed));

        public HistogramSeries()
        {
            this.Tickness = 0;
            this.SeriesName = "Histogram";
        }

        internal override async void Drawing()
        {
            if (OwnerChart == null || OwnerChart.XSource == null || OwnerChart.XAxis == null || OwnerChart.YAxis == null) return;
            Geometries = await GetGeometries(OwnerChart.XSource, null, OwnerChart.XAxis, OwnerChart.YAxis, OwnerChart.Indexs, OwnerChart.XValueConverter, OwnerChart.YValueConverter, this.Fill, this.Stroke, this.GetGradientColor);
            Drawing(Geometries);
        }

        internal override Task<List<StreamColor>> GetGeometries(IList xSource, IList ySource, IAxis xAxis, IAxis yAxis, IList<int> indexs, Func<object, double> xValueConverter, Func<object, double> yValueConverter, Color fill, Color stroke, Func<long, long, Color, Color> getGradientColor)
        {
            var pointcount = xSource.Count;
            Func<double, double> valuetoaxisvalue = xAxis.ValueToAxisValue;
            Func<double, double> yvaluetoaxisvalue = yAxis.ValueToAxisValue;
            Func<double, ValueLocationConvertParam, double> ygetlocation = yAxis.GetValueLocation;
            var count = Count;
            var maxaxis = xAxis.MaxAxis;
            var minaxis = xAxis.MinAxis;
            var max = xAxis.Max;
            var min = xAxis.Min;
            var range = (maxaxis - minaxis) / count;
            var xGetLocationParam = xAxis.GetConvertParam();
            var height = this.ActualHeight;
            var areas = this.OwnerChart is ChartWithGraphicals tempchart && tempchart.AllAreas != null ? tempchart.AllAreas.ToArray() : null;
            var maxDegreeOfParallelism = MaxDegreeOfParallelism;
            if (CancelTokenSource != null)
            {
                CancelTokenSource.Cancel();
                CancelTokenSource.Dispose();
            }
            CancelTokenSource = new CancellationTokenSource();
            var response = Task.Factory.StartNew((tkn) =>
            {
                if (tkn is CancellationToken token)
                {
                    List<StreamColor> streams = new List<StreamColor>();

                    #region 总直方图
                    ConcurrentDictionary<double[], double> datas = new ConcurrentDictionary<double[], double>();//512份区间内的数量
                    ConcurrentDictionary<int, double[]> dictionaries = new ConcurrentDictionary<int, double[]>();//区间顺序
                    for (int i = 0; i <= count; i++)
                    {
                        if (token.IsCancellationRequested) return null;
                        var x1 = xAxis.GetAxisValueLocation(range * i + minaxis, xGetLocationParam);
                        var x2 = xAxis.GetAxisValueLocation(range * (i + 1) + minaxis, xGetLocationParam);
                        var key = new double[] { x1, x2 };
                        datas[key] = 0;
                        dictionaries[i] = key;
                    }
                    if (indexs == null)
                    {
                        var result = Parallel.For(0, pointcount, new ParallelOptions() { CancellationToken = token, MaxDegreeOfParallelism = maxDegreeOfParallelism }, (i) =>
                        {
                            var tempv = xValueConverter == null ? Convert.ToDouble(xSource[i]) : xValueConverter(xSource[i]);
                            AddValue(datas, dictionaries, tempv, max, min, valuetoaxisvalue, minaxis, range);
                        });
                        if (!result.IsCompleted) return null;
                    }
                    else
                    {
                        var result = Parallel.ForEach(indexs, new ParallelOptions() { CancellationToken = token, MaxDegreeOfParallelism = maxDegreeOfParallelism }, (i) =>
                        {
                            var tempv = xValueConverter == null ? Convert.ToDouble(xSource[i]) : xValueConverter(xSource[i]);
                            AddValue(datas, dictionaries, tempv, max, min, valuetoaxisvalue, minaxis, range);
                        });
                        if (!result.IsCompleted) return null;
                    }
                    var yMax = datas.Max(p => p.Value) * 1.2;
                    yAxis.Dispatcher.Invoke(() =>
                    {
                        yAxis.Max = yMax;
                        yAxis.Min = 0;
                        yAxis.Drawing();
                    });
                    var yconvertparam = new ValueLocationConvertParam()
                    {
                        MaxAxis = yvaluetoaxisvalue(yMax),
                        MinAxis = yvaluetoaxisvalue(0),
                        Length = yAxis.ActualHeight,
                        XYType = AxisType.Y
                    };
                    var streamGeometry = new StreamGeometry() { FillRule = FillRule.EvenOdd };
                    using (StreamGeometryContext sgc = streamGeometry.Open())
                    {
                        List<Point> points = new List<Point>();
                        var tempdatas = datas.OrderBy(p => p.Key[0]);
                        foreach (var data in tempdatas)
                        {
                            if (token.IsCancellationRequested) return null;
                            var yvalue = ygetlocation(data.Value, yconvertparam);
                            if (double.IsNaN(data.Key[0]) || double.IsInfinity(data.Key[0])
                            || double.IsNaN(data.Key[1]) || double.IsInfinity(data.Key[1])
                            || double.IsNaN(yvalue) || double.IsInfinity(yvalue)) continue;
                            points.Add(new Point(data.Key[0], yvalue));
                            points.Add(new Point(data.Key[1], yvalue));
                        }
                        if (token.IsCancellationRequested) return null;
                        if (points.Count > 0)
                        {
                            points.Add(new Point(points[points.Count - 1].X, height));
                            points.Add(new Point(points[0].X, height));
                            sgc.BeginFigure(new Point(points[0].X, height), true, true);
                            sgc.PolyLineTo(points, false, false);
                        }
                        else
                        {
                            sgc.BeginFigure(new Point(0, 0), true, true);
                            sgc.LineTo(new Point(0, 0), false, false);
                        }
                        sgc.Close();
                    }
                    streamGeometry.Freeze();
                    streams.Add(new StreamColor() { Stream = streamGeometry, Fill = fill, Stroke = stroke });
                    #endregion

                    #region 门的直方图
                    if (areas != null)
                    {
                        foreach (var area in areas)
                        {
                            if (area == null || area.InsideIndexs == null || area.InsideIndexs.Count <= 0) continue;
                            ConcurrentDictionary<double[], double> areadatas = new ConcurrentDictionary<double[], double>();//512份区间内的数量
                            ConcurrentDictionary<int, double[]> areadictionaries = new ConcurrentDictionary<int, double[]>();//区间顺序
                            for (int i = 0; i <= count; i++)
                            {
                                if (token.IsCancellationRequested) return null;
                                var x1 = xAxis.GetAxisValueLocation(range * i + minaxis, xGetLocationParam);
                                var x2 = xAxis.GetAxisValueLocation(range * (i + 1) + minaxis, xGetLocationParam);
                                var key = new double[] { x1, x2 };
                                areadatas[key] = 0;
                                areadictionaries[i] = key;
                            }
                            var tempindexs = area.InsideIndexs;
                            if (indexs != null) tempindexs = tempindexs.Intersect(indexs).ToArray();//取交集
                            var result = Parallel.ForEach(tempindexs, new ParallelOptions() { CancellationToken = token, MaxDegreeOfParallelism = maxDegreeOfParallelism }, (i) =>
                            {
                                var tempv = xValueConverter == null ? Convert.ToDouble(xSource[i]) : xValueConverter(xSource[i]);
                                AddValue(areadatas, areadictionaries, tempv, max, min, valuetoaxisvalue, minaxis, range);
                            });
                            if (!result.IsCompleted) return null;
                            var stream = new StreamGeometry() { FillRule = FillRule.EvenOdd };
                            using (StreamGeometryContext sgc = stream.Open())
                            {
                                List<Point> points = new List<Point>();
                                var tempareadatas = areadatas.OrderBy(p => p.Key[0]);
                                foreach (var data in tempareadatas)
                                {
                                    if (token.IsCancellationRequested) return null;
                                    var yvalue = ygetlocation(data.Value, yconvertparam);
                                    if (double.IsNaN(data.Key[0]) || double.IsInfinity(data.Key[0])
                                    || double.IsNaN(data.Key[1]) || double.IsInfinity(data.Key[1])
                                    || double.IsNaN(yvalue) || double.IsInfinity(yvalue)) continue;
                                    points.Add(new Point(data.Key[0], yvalue));
                                    points.Add(new Point(data.Key[1], yvalue));
                                }
                                if (token.IsCancellationRequested) return null;
                                if (points.Count > 0)
                                {
                                    points.Add(new Point(points[points.Count - 1].X, height));
                                    points.Add(new Point(points[0].X, height));
                                    sgc.BeginFigure(new Point(points[0].X, height), true, true);
                                    sgc.PolyLineTo(points, false, false);
                                }
                                else
                                {
                                    sgc.BeginFigure(new Point(0, 0), true, true);
                                    sgc.LineTo(new Point(0, 0), false, false);
                                }
                                sgc.Close();
                            }
                            stream.Freeze();
                            streams.Add(new StreamColor() { Stream = stream, Fill = area.DisplayColor, Stroke = area.DisplayColor, Area = area });
                        }
                    }
                    #endregion

                    return streams;
                }
                else return null;
            }, CancelTokenSource.Token, CancelTokenSource.Token);
            CancelTokenSource.Dispose();
            CancelTokenSource = null;
            return response;
        }

        private void AddValue(ConcurrentDictionary<double[], double> datas, ConcurrentDictionary<int, double[]> dictionaries, double tempv, double max, double min, Func<double, double> valuetoaxisvalue, double minaxis, double range)
        {
            if (tempv > max || tempv < min) return;
            var value = (valuetoaxisvalue(tempv) - minaxis) / range;
            if (double.IsNaN(value) || double.IsInfinity(value)) return;
            var temp = value >= 0 ? (int)value : (int)value - 1;
            var td = dictionaries[temp];
            datas[td] += 1;
        }

        #region 区域显示
        /// <summary>
        /// 添加区域
        /// </summary>
        /// <param name="area"></param>
        internal override void AddArea(Graphical.GraphicalArea area, IList xSource, IList ySource, IAxis xAxis, IAxis yAxis, IList<int> indexs, Func<object, double> xValueConverter, Func<object, double> yValueConverter)
        {
            if (area == null || Geometries.FirstOrDefault(p => p.Area == area) != null) return;
            var stream = GetAreaStream(area, xSource, ySource, xAxis, yAxis, indexs, xValueConverter, yValueConverter);
            if (stream != null)
            {
                if (Geometries.FirstOrDefault(p => p.Area == area) != null) return;
                Geometries.Add(new StreamColor() { Stream = stream, Area = area, Fill = area.DisplayColor, Stroke = area.DisplayColor });
                Drawing(Geometries);
            }
        }
        /// <summary>
        /// 删除区域
        /// </summary>
        /// <param name="area"></param>
        internal override void RemoveArea(Graphical.GraphicalArea area)
        {
            if (Geometries != null && area != null)
            {
                var temp = Geometries.FirstOrDefault(p => p.Area == area);
                if (temp.Stream != null)
                {
                    Geometries.Remove(temp);
                    Drawing(Geometries);
                }
            }
        }
        /// <summary>
        /// 更新区域显示颜色
        /// </summary>
        /// <param name="area"></param>
        internal override void UpdateAreaDisplayColor(Graphical.GraphicalArea area)
        {
            if (Geometries != null && area != null)
            {
                var temp = Geometries.FirstOrDefault(p => p.Area == area);
                if (temp.Stream != null)
                {
                    temp.Fill = temp.Stroke = area.DisplayColor;
                    Drawing(Geometries);
                }
            }
        }
        /// <summary>
        /// 更新区域索引
        /// </summary>
        /// <param name="area"></param>
        internal override void UpdateAreaIndexs(Graphical.GraphicalArea area, IList xSource, IList ySource, IAxis xAxis, IAxis yAxis, IList<int> indexs, Func<object, double> xValueConverter, Func<object, double> yValueConverter)
        {
            if (area == null) return;
            var geometries = Geometries.FirstOrDefault(p => p.Area == area);
            if (geometries == null)
            {
                AddArea(area, xSource, ySource, xAxis, yAxis, indexs, xValueConverter, yValueConverter);
            }
            else
            {
                var stream = GetAreaStream(area, xSource, ySource, xAxis, yAxis, indexs, xValueConverter, yValueConverter);
                if (stream != null)
                {
                    geometries.Stream = stream;
                    Drawing(Geometries);
                }
            }
        }

        private StreamGeometry GetAreaStream(Graphical.GraphicalArea area, IList xSource, IList ySource, IAxis xAxis, IAxis yAxis, IList<int> indexs, Func<object, double> xValueConverter, Func<object, double> yValueConverter)
        {
            if (area == null || area.InsideIndexs == null || area.InsideIndexs.Count <= 0) return null;
            Func<double, double> xvaluetoaxisvalue = xAxis.ValueToAxisValue;
            ValueLocationConvertParam yconvertparam = yAxis.GetConvertParam();
            Func<double, ValueLocationConvertParam, double> ygetlocation = yAxis.GetValueLocation;
            var count = Count;
            var maxaxis = xAxis.MaxAxis;
            var minaxis = xAxis.MinAxis;
            var max = xAxis.Max;
            var min = xAxis.Min;
            var range = (maxaxis - minaxis) / count;
            var xGetLocationParam = xAxis.GetConvertParam();
            var height = this.ActualHeight;
            var maxDegreeOfParallelism = MaxDegreeOfParallelism;
            ConcurrentDictionary<double[], double> areadatas = new ConcurrentDictionary<double[], double>();//512份区间内的数量
            ConcurrentDictionary<int, double[]> areadictionaries = new ConcurrentDictionary<int, double[]>();//区间顺序
            for (int i = 0; i <= count; i++)
            {
                var x1 = xAxis.GetAxisValueLocation(range * i + minaxis, xGetLocationParam);
                var x2 = xAxis.GetAxisValueLocation(range * (i + 1) + minaxis, xGetLocationParam);
                var key = new double[] { x1, x2 };
                areadatas[key] = 0;
                areadictionaries[i] = key;
            }
            var tempindexs = area.InsideIndexs;
            if (indexs != null) tempindexs = tempindexs.Intersect(indexs).ToArray();//取交集
            var result = Parallel.ForEach(tempindexs, new ParallelOptions() { MaxDegreeOfParallelism = maxDegreeOfParallelism }, (i) =>
            {
                var tempv = xValueConverter == null ? Convert.ToDouble(xSource[i]) : xValueConverter(xSource[i]);
                AddValue(areadatas, areadictionaries, tempv, max, min, xvaluetoaxisvalue, minaxis, range);
            });
            if (!result.IsCompleted) return null;
            var stream = new StreamGeometry() { FillRule = FillRule.EvenOdd };
            using (StreamGeometryContext sgc = stream.Open())
            {
                List<Point> points = new List<Point>();
                var tempareadatas = areadatas.OrderBy(p => p.Key[0]);
                foreach (var data in tempareadatas)
                {
                    var yvalue = ygetlocation(data.Value, yconvertparam);
                    if (double.IsNaN(data.Key[0]) || double.IsInfinity(data.Key[0])
                    || double.IsNaN(data.Key[1]) || double.IsInfinity(data.Key[1])
                    || double.IsNaN(yvalue) || double.IsInfinity(yvalue)) continue;
                    points.Add(new Point(data.Key[0], yvalue));
                    points.Add(new Point(data.Key[1], yvalue));
                }
                if (points.Count > 0)
                {
                    points.Add(new Point(points[points.Count - 1].X, height));
                    points.Add(new Point(points[0].X, height));
                    sgc.BeginFigure(new Point(points[0].X, height), true, true);
                    sgc.PolyLineTo(points, false, false);
                }
                else
                {
                    sgc.BeginFigure(new Point(0, 0), true, true);
                    sgc.LineTo(new Point(0, 0), false, false);
                }
                sgc.Close();
            }
            stream.Freeze();
            return stream;
        }
        #endregion
    }
}
